package com.linkgie.galaxyframework.utils.io;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Properties;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;

import com.linkgie.galaxyframework.utils.IOStatusException;
import com.linkgie.galaxyframework.utils.ReadonlyProperties;
import com.linkgie.galaxyframework.utils.SortedProperties;

public abstract class PropertiesUtils {

	private static final Function<String, String> ORIGIN_KEY_MAPPER = (key) -> key;

	private static final Function<String, String> TRIMING_KEY_MAPPER = (key) -> key.trim();

	private PropertiesUtils() {
	}

	public static Builder builder(){
		return new Builder();
	}


	public static Properties readFromText(String textProperties) {
		Properties props = new Properties();
		try (StringReader reader =  new StringReader(textProperties)) {
			props.load(reader);
			return props;
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
	}

	public static Properties loadFromFile(File propsFile) {
		byte[] content = FileUtils.readBytes(propsFile);
		return load(content, FileUtils.DEFAULT_CHARSET);
	}

	public static Properties loadFromFile(File propsFile, String charset) {
		byte[] content = FileUtils.readBytes(propsFile);
		return load(content, charset);
	}

	public static Properties load(InputStream in, String charset) {
		Properties props = new Properties();
		try (InputStreamReader reader = new InputStreamReader(in, charset)) {
			props.load(reader);
		} catch (IOException e) {
			throw new IOStatusException(e.getMessage(), e);
		}
		return props;
	}

	public static Properties load(byte[] bytes, String charset) {
		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
		return load(in, charset);
	}

	public static Properties load(byte[] bytes) {
		ByteArrayInputStream in = new ByteArrayInputStream(bytes);
		return load(in, FileUtils.DEFAULT_CHARSET);
	}

	/**
	 * 合并两个 properties ；
	 * 
	 * @param props 要将其它值合并进来的属性集合；操作将对其产生修改；
	 * @param from  属性值将要合并进入其它属性集合；操作不对其产生修改；
	 */
	public static void mergeFrom(Properties props, Properties from) {
		mergeFrom(props, from, null);
	}

	/**
	 * 合并两个 properties ；
	 * 
	 * @param props              要将其它值合并进来的属性集合；操作将对其产生修改；
	 * @param from               属性值将要合并进入其它属性集合；操作不对其产生修改；
	 * @param propertyNamePrefix 属性名称前缀；
	 */
	public static void mergeFrom(Properties props, Properties from, String propertyNamePrefix) {
		if (propertyNamePrefix == null || propertyNamePrefix.length() == 0) {
			for (String name : from.stringPropertyNames()) {
				props.setProperty(name, from.getProperty(name));
			}
		} else {
			for (String name : from.stringPropertyNames()) {
				props.setProperty(propertyNamePrefix + name, from.getProperty(name));
			}
		}
	}

	/**
	 * 获取指定 properties 中以指定的前缀开头的子集；
	 * <p>
	 * 方法不会修改原来的 properties;
	 * 
	 * @param props              要抽取的属性集合；
	 * @param propertyNamePrefix 属性名称前缀；
	 * @param trimPrefix         是否在复制的新的属性集合去掉指定的前缀；
	 * @return properties
	 */
	public static Properties subset(Properties props, String propertyNamePrefix, boolean trimPrefix) {
		return subset(props, (name) -> name.startsWith(propertyNamePrefix),
				(name) -> trimPrefix ? name.substring(propertyNamePrefix.length()) : name);

		// Properties subProperties = new Properties();
		// Set<String> names = props.stringPropertyNames();
		// String newName;
		// for (String name : names) {
		// if (name.startsWith(propertyNamePrefix)) {
		// newName = name;
		// if (trimPrefix) {
		// newName = name.substring(propertyNamePrefix.length());
		// }
		// subProperties.setProperty(newName, props.getProperty(name));
		// }
		// }
		//
		// return subProperties;

	}

	/**
	 * 获取指定 properties 的子集；
	 * 
	 * <p>
	 * 方法不会修改原来的 properties;
	 * 
	 * @param props           属性集合；
	 * @param subsetPredicate 属性的断言函数；
	 * @param keyMapFunction  属性的映射函数；
	 *                        <p>
	 *                        映射函数将满足断言函数的属性转换为新的属性；
	 *                        <p>
	 *                        如果不指定映射函数，则保留原属性名输出到返回的子集；
	 * @return 满足断言的子集；
	 */
	public static Properties subset(Properties props, Predicate<String> subsetPredicate,
			Function<String, String> keyMapFunction) {
		Properties subProperties = new Properties();
		Set<String> names = props.stringPropertyNames();
		String newName;
		Function<String, String> keyMapper = keyMapFunction == null ? ORIGIN_KEY_MAPPER : keyMapFunction;
		for (String name : names) {
			if (subsetPredicate.test(name)) {
				newName = keyMapper.apply(name);
				subProperties.setProperty(newName, props.getProperty(name));
			}
		}

		return subProperties;
	}

	/**
	 * 根据指定的断言函数分割 properties ，满足断言的子集以新的 properties 返回，留下不满足的部分；
	 * 
	 * <p>
	 * 注：此方法修改原来的 properties，移除满足断言的子集，留下不满足断言的部分;
	 * 
	 * @param props           属性集合；
	 * @param subsetPredicate 属性的断言函数；
	 * @return 满足断言的属性子集；
	 */
	public static Properties split(Properties props, Predicate<String> subsetPredicate) {
		return split(props, subsetPredicate, null);
	}

	/**
	 * 根据指定的断言函数分割 properties ，满足断言的子集以新的 properties 返回，留下不满足的部分；
	 * 
	 * <p>
	 * 注：此方法修改原来的 properties，移除满足断言的子集，留下不满足断言的部分;
	 * 
	 * @param props           属性集合；
	 * @param subsetPredicate 属性的断言函数；
	 * @param keyMapFunction  属性的映射函数；
	 *                        <p>
	 *                        映射函数将满足断言函数的属性转换为新的属性；
	 *                        <p>
	 *                        如果不指定映射函数，则保留原属性名输出到返回的子集；
	 * @return 满足断言的属性子集；
	 */
	public static Properties split(Properties props, Predicate<String> subsetPredicate,
			Function<String, String> keyMapFunction) {
		Properties subProperties;
		if (props instanceof SortedProperties) {
			subProperties = new SortedProperties();
		} else {
			subProperties = new Properties();
		}

		Set<String> names = props.stringPropertyNames();// 注：属性名称集合不依赖原来的 properties ；

		String newName;
		Function<String, String> keyMapper = keyMapFunction == null ? ORIGIN_KEY_MAPPER : keyMapFunction;
		for (String name : names) {
			if (subsetPredicate.test(name)) {
				newName = keyMapper.apply(name);
				String value = (String) props.remove(name);
				subProperties.setProperty(newName, value);
			}
		}

		return subProperties;
	}

	public static Properties cloneFrom(Properties props) {
		Properties newProps = new Properties();
		copy(props, newProps);
		return newProps;
	}

	public static ReadonlyProperties readonly(Properties props) {
		return new ReadonlyPropertiesImpl(props);
	}

	public static SortedProperties sort(Properties props) {
		SortedProperties sortedProps = new SortedProperties();
		copy(props, sortedProps);
		return sortedProps;
	}

	public static void copy(Properties src, Properties dest) {
		if (src == null || src.isEmpty()) {
			return;
		}
		Set<String> names = src.stringPropertyNames();
		for (String name : names) {
			dest.setProperty(name, src.getProperty(name));
		}
	}

	public static void setPrefix(Properties properties, String prefix) {
		Set<String> names = properties.stringPropertyNames();
		for (String name : names) {
			String newName = prefix + name;
			String value = (String) properties.remove(name);
			properties.setProperty(newName, value);
		}
	}

	public static byte[] toBytes(Properties props) {
		return toBytes(props, FileUtils.DEFAULT_CHARSET);
	}

	public static byte[] toBytes(Properties props, String charsetName) {
		try {
			ByteArrayOutputStream out = new ByteArrayOutputStream();
			OutputStreamWriter writer = new OutputStreamWriter(out, charsetName);
			try {
				props.store(writer, null);
				writer.flush();
			} finally {
				writer.close();
			}
			return out.toByteArray();
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static String toString(Properties props) {
		try {
			StringWriter writer = new StringWriter();
			try {
				props.store(writer, null);
				writer.flush();
			} finally {
				writer.close();
			}
			return writer.toString();
		} catch (IOException e) {
			throw new IllegalStateException(e.getMessage(), e);
		}
	}

	public static int getInt(Properties props, String key) {
		String value = getRequiredProperty(props, key);
		return Integer.parseInt(value);
	}

	public static boolean getBoolean(Properties props, String key) {
		String value = getRequiredProperty(props, key);
		return Boolean.parseBoolean(value);
	}

	public static boolean getBooleanOptional(Properties props, String key, boolean defaultValue) {
		String value = getProperty(props, key, false);
		if (value == null) {
			return defaultValue;
		}
		return Boolean.parseBoolean(value);
	}

	/**
	 * 返回指定的属性； <br>
	 * 如果不存在，或者返回值为空（null 或 空白字符），则抛出 {@link IllegalArgumentException} 异常；
	 * 
	 * @param props props
	 * @param key   key
	 * @return String
	 */
	public static String getRequiredProperty(Properties props, String key) {
		return getProperty(props, key, true);
	}

	public static String getOptionalProperty(Properties props, String key) {
		return getProperty(props, key, false);
	}

	public static String getOptionalProperty(Properties props, String key, String defaultValue) {
		String value = getProperty(props, key, false);
		if (value == null) {
			return defaultValue;
		}
		return value;
	}

	/**
	 * 返回指定的属性； <br>
	 * 
	 * @param props    属性表；
	 * @param key      要查找的 key；
	 * @param required 值为 false 时，如果不存在则返回 null；值为 true 时，如果不存在，或者返回值为空（null 或
	 *                 空白字符），则抛出 {@link IllegalArgumentException} 异常；
	 * @return
	 */
	public static String getProperty(Properties props, String key, boolean required) {
		String value = props.getProperty(key);
		if (value == null) {
			if (required) {
				throw new IllegalArgumentException("Miss property[" + key + "]!");
			}
			return null;
		}
		value = value.trim();
		if (value.length() == 0) {
			if (required) {
				throw new IllegalArgumentException("Miss property[" + key + "]!");
			}
			return null;
		}
		return value;

	}

	public static class Builder {

		private Properties properties = new Properties();

		private Builder() {
		}

		public Builder set(String key, String value) {
			properties.setProperty(key, value);
			return this;
		}

		public Properties properties() {
			return properties;
		}
	}

	private static class ReadonlyPropertiesImpl implements ReadonlyProperties {

		private static final long serialVersionUID = -4045144904347051885L;

		private Properties properties;

		public ReadonlyPropertiesImpl(Properties properties) {
			this.properties = properties;
		}

		@Override
		public String getProperty(String key) {
			return properties.getProperty(key);
		}

		@Override
		public String getProperty(String key, String defaultValue) {
			return properties.getProperty(key, defaultValue);
		}

		@Override
		public Set<String> propertyKeys() {
			return properties.stringPropertyNames();
		}

	}

}
